/*
 * StorageManager.cpp
 *
 *  Created on: Sep 25, 2011
 *      Author: Daniel
 */

#include <iostream>
#include <math.h>
#include <limits.h>

#include "StorageManager.h"

//Public methods
StorageManager::StorageManager() {
	_bookRoot = new Page[NUMBER_OF_PAGES]();
	_recordStructure = new(_bookRoot) record_structure;
	_recordStructure->num_pages = NUMBER_OF_PAGES;
	_recordStructure->page_size = sizeof(Page);
	_recordStructure->num_bits_per_int = NUM_BITS; //check this on each new compiler in debug mode to make sure is 32 or 64
	_fileMapPage = &_bookRoot[1];

	InitializeSpaceMap();
}

StorageManager::~StorageManager() {
	// TODO Auto-generated destructor stub
}

Page *StorageManager::AllocatePage() {
	//TODO: make get/mark atomic operation or
	// make MarkPage check-get atomically and return success
	int freePg = GetFreePageNumber();
	MarkPage(freePg, true);
	if(freePg+1 > _recordStructure->num_pages)
		_nextAvailablePage = 0;
	else
		_nextAvailablePage = freePg+1;

	return &_bookRoot[freePg];
}

bool StorageManager::DeallocatePage(Page &pg) {
	int pgNm = (int)(&pg - _bookRoot); //verify pointer arithmetic takes into account sizeof(Page))
	if(pgNm > 2 && pgNm < (_recordStructure->num_pages - 1)) {
		MarkPage(pgNm, false); //Mark unused if within bounds
		return true;
	}
	else
		return false;
}

void StorageManager::PrintSpaceMap(int numRecordsToPrint, ostream &out) {
	//TODO: what information to output for space map?
	//Summary or detailed?
	//Allocation or record info?
	out << "Storage Manager Record Structure" << endl <<
			"--------------------------------" << endl <<
			" TOTAL pages: " << _recordStructure->num_pages << endl <<
			" page size (bytes): " << _recordStructure->page_size << endl <<
			" #bits/int: " << _recordStructure->num_bits_per_int << endl <<
			" TOTAL spacemap pages: " << _recordStructure->num_spacemap_pages << endl <<
			" Next free page: " << _nextAvailablePage << endl << endl;

	if(numRecordsToPrint == 0)
		return;

	int pageDumpCount = (numRecordsToPrint < 0) ? _nextAvailablePage : numRecordsToPrint;

	out << "Spacemap Dump - Print " << pageDumpCount << endl
			<<       "-------------" << endl;
	for(int cnt = 0; cnt < pageDumpCount; cnt++) {
		out << " Page " << cnt << ": " << IsUsed(cnt + _recordStructure->num_spacemap_pages + 2) << endl;
	}

	Student *std;
	out << endl << "Record Dump" << endl
			<<               "-----------" << endl;
	for(int cnt = 0; cnt < pageDumpCount; cnt++) {
		std = FetchStudent(_firstRecordPage[cnt]);
		out << " Page " << cnt << ": " << std->GetStudentString() << endl;
	}
}

//Hard-coded convenience functions for project
void StorageManager::PrintStudent(Page &p) {
	Student *std = FetchStudent(p);
	cout << std->GetStudentString() << endl;
}

Student *StorageManager::FetchStudent(Page &p) {
	return (Student *)&p;
}

//Private methods
void StorageManager::InitializeSpaceMap() {
	//Calculate spacemap bounds
	_firstSpaceMapPage = &_bookRoot[2];
	double tmp = _recordStructure->num_pages / 8.0 / _recordStructure->page_size;
	_recordStructure->num_spacemap_pages = (int) ceil(tmp);

	//Initialize spacemap pages to 0 (unused)
	for(int idx = 0; idx < _recordStructure->num_spacemap_pages; idx++) {
		ClearPage(_firstSpaceMapPage + idx);
	}

	//Mark used pages (record struct, filemap, spacemap)
	MarkPage(0, true); //record structure
	MarkPage(1, true); //filemap
	for(int idx = 0; idx < _recordStructure->num_spacemap_pages; idx++) {
		MarkPage(idx + 2, true); //spacemap
	}
	_nextAvailablePage = _recordStructure->num_spacemap_pages + 2; //page numbers start a 0, 2 are taken by record structure and file
	_firstRecordPage = &_bookRoot[_nextAvailablePage];
}

//Manually write zeros to all bytes in the page
void StorageManager::ClearPage(Page *pg) {
	int bytes = sizeof(Page);
	char *bytePtr = (char *)pg;
	for(int idx = 0; idx < bytes; idx++)
		bytePtr[idx] = '\0';
}

//NOTE: if all pages full, infinite loop occurs!
int StorageManager::GetFreePageNumber() {
	int *startPtr = (int*)_firstSpaceMapPage;
	startPtr = startPtr + (int)(_nextAvailablePage / _recordStructure->num_bits_per_int);

	int *ptr;
	for(ptr = startPtr; *ptr != -1; ptr = ptr + 1) {
		if(ptr >= (int*)_firstRecordPage)
			ptr = (int*)_firstSpaceMapPage; //if past spacemap area, wrap around to begining and keep looking
	}

	int intoffset = (ptr - (int*)_firstSpaceMapPage);
	int mask = INT_MIN; //0b1000...
	int bitoffset;
	for(bitoffset = 0; ((*ptr) & mask) != 0; bitoffset++)
		mask = mask >> 1;

	return intoffset * _recordStructure->num_bits_per_int + bitoffset;
}

bool StorageManager::IsUsed(int pageNum) {
	int *ptr = (int*)_firstSpaceMapPage + pageNum / _recordStructure->num_bits_per_int;
	//see MarkPage logic for explanation and options for generating mask
	int lshift = _recordStructure->num_bits_per_int - (pageNum % _recordStructure->num_bits_per_int) - 1;
	int mask = 1 << lshift;

	return (*ptr & mask);
}

void StorageManager::MarkPage(int pageNum, bool used) {
	int *ptr = (int*)_firstSpaceMapPage + pageNum / _recordStructure->num_bits_per_int;
	//int rshift = pageNum % _recordStructure->num_bits_per_int;
	//int mask = (INT_MIN >> rshift) ^ (INT_MIN >> (rshift - 1));
	int lshift = _recordStructure->num_bits_per_int - (pageNum % _recordStructure->num_bits_per_int) - 1; //-1 because if rshift == 0, need to lshift 31 (not 32) spaces
	int mask = 1 << lshift;

	if(used)
		*ptr = *ptr | mask; //mark as used by adding true bit into spacemap (OR)
	else
		*ptr = *ptr & (-1 ^ mask); //mark as unused by removing bit from spacemap (AND)
}
